Extensible Effect
H勉強会でやるので適当なメモ書き
モナド変換子つらい問題
モナド変換子は習って新しいおもちゃを手に入れた幼稚園児のように使っていくとすぐに以下のような不満に突き当たる
モナド変換子を使ってどんどんcomputationを付け加えていくと重くなる
複数のモナドの組み合わせなので,>>=やreturnするだけでも全モナドで計算が必要でモナドをネストするだけ計算の効率が落ちる
途中で合成順を変えられない
いちいち複数回liftする必要がある
mtlライブラリのMonadIOなどのように型クラスのadhoc多相を用いて変換子のモナドアクションを定義しておけばliftする必要はないが,変換子を提供するライブラリ作成者が提供する変換子の数だけよく使うMonadIOなどで6種,クラスを定義する際は自前のモナドに加えて12種インスタンスを作らされる.無理.
これらの問題を解決するのが拡張可能作用(Extensible Effect)である.これもextensibleパッケージに含まれる.最強か?
詳しい理論は置いといてどう使うのよ
拡張可能レコードの値にextensibleで提供されているモナドを突っ込む感じ
フィールド名は省略可
ReaderT r (WriterT w (State s))はEff '[ReaderDef r, WriterDef w, StateDef s]になる
左から順に計算が実行されていく
ここでも型レベルリストの順番に注意する必要がある,順番が違えば計算結果は異なるものとなる.
同じ作用を持つ計算をフィールド名で区別することができる.実際には次のようになる.
code: Eff1.hs
-- Associate "key" (MonadEff) xsの制約計算でフィールド名を指定している
test :: (Associate "foo" (WriterEff String) xs, Associate "bar" (WriterEff String) xs) => Eff xs ()
test = do
-- runWriterEffまではモナド変換子と同じ.最後にleaveEffをつかってEffを剥がす
-- @はTypeApplications拡張で導入される
leaveEff $ runWriterEff @ "foo" $ runWriterEff @ "bar" test
>> (((),"hogefuga"),"Hello world")
liftEffで自前のアクションの持ち上げ,decEffectsにGADTsを食わせることでTHを使ったアクションの自動定義を行うこともできるぞ!
速いのか?
モナド3つくらいまでならモナド変換子のほうが速いが,200ns位の差.
で,理論は?
Oleg神の論文から.
モナド(変換子)をcomputationの委譲としてみるという発想から,つまりはある計算を司るハンドラ(WriterTなど)が計算したらその後は他の計算を司るハンドラに任せたといって渡していくという考えを実装したものになっている.comutationのスタックを消費していくイメージといえば良いか.runWriterEffなどが計算を行うと,自分ができる計算のみを行ってできないものはそのままにしておく.その計算済みの結果を受け取ってまたrunHogeEffが同様に自分ができる計算のみを行う.この結果がIOやSTなどの底のモナドまで計算を行えば,計算が行われていない計算はのこっていないので,leaveEffでEffを取り除くことができる.このcomputationのスタックそのものを拡張可能レコードで表現したのがextensibleのEffである.
雑な解説なので間違ってるかも.
その他の話
fumievalさん(作者)の記事読むのが最強でしょ
多分これみとけば完璧